﻿using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

using NUnit.Framework;

namespace Working.Teste
{
  public abstract class TesteEntidadeDomínio<T>: ITesteSerialização
  {
    public TesteEntidadeDomínio()
    {
      valoresParaTeste = new Dictionary<string, object>();
    }

    protected Type TipoEntidade
    {
      get { return typeof(T); }
    }

    protected Dictionary<string, object> ValoresParaTeste
    {
      get { return valoresParaTeste; }
    }
    private Dictionary<string, object> valoresParaTeste;

    public virtual void TestarSerialização()
    {
      if(TipoEntidade.IsSerializable && TipoEntidade.IsAssignableFrom(typeof(ISerializable)))
        Assert.Ignore("Aviso: Testes sobre a serialização da entidade está pendente.");
    }

    public virtual void TestarGetObjectData()
    {
      if(TipoEntidade.IsSerializable && TipoEntidade.IsSubclassOf(typeof(ISerializable)))
        Assert.Ignore("Aviso: Testes sobre a serialização da entidade está pendente.");
    }

    protected virtual void TestarPropriedades(T instância, Dictionary<string, object> valores)
    {
      if(valores != null && valores.Count > 0)
      {
        foreach(KeyValuePair<string, object> pair in valores)
        {
          string propertyName = pair.Key;
          object propertyValue = pair.Value;
          object returnValue = null;

          AtribuirValorNaPropriedade(instância, propertyName, propertyValue);
          returnValue = ObterValorDaPropriedade(instância, propertyName);

          Assert.AreEqual(returnValue, propertyValue,
            "Propriedade '{0}' não retornou o valor experado.", propertyName);
        }
      }
    }

    private void AtribuirValorNaPropriedade(T instância, string nomeDaPropriedade, object valorDaPropriedade)
    {
      if(instância == null)
        throw new ArgumentException(string.Format("Parâmetro '{0}' não pode ser nulo.",
          "instância"));

      if(string.IsNullOrEmpty(nomeDaPropriedade))
        throw new ArgumentException(string.Format("Parâmetro '{0}' não pode ser nulo ou estar em branco.",
          "nomeDaPropriedade"));

      PropertyInfo property = TipoEntidade.GetProperty(nomeDaPropriedade,
        BindingFlags.Public | BindingFlags.Instance);

      if(property == null)
        throw new Exception(string.Format("Propriedade '{0}' não pode ser localizada.",
          nomeDaPropriedade));

      if(!property.CanWrite)
        throw new Exception(string.Format("O valor da propriedade '{0}' não pode ser modificado.",
          nomeDaPropriedade));

      property.SetValue(instância, valorDaPropriedade, null);
    }

    private object ObterValorDaPropriedade(T instância, string nomeDaPropriedade)
    {
      if(instância == null)
        throw new ArgumentException(string.Format("Parâmetro '{0}' não pode ser nulo.",
          "instância"));

      if(string.IsNullOrEmpty(nomeDaPropriedade))
        throw new ArgumentException(string.Format("Parâmetro '{0}' não pode ser nulo ou estar em branco.",
          "nomeDaPropriedade"));

      PropertyInfo property = TipoEntidade.GetProperty(nomeDaPropriedade,
        BindingFlags.Public | BindingFlags.Instance);

      if(property == null)
        throw new Exception(string.Format("Propriedade '{0}' não pode ser localizada.",
          nomeDaPropriedade));

      if(!property.CanRead)
        throw new Exception(string.Format("O valor da propriedade '{0}' não pode ser obtido.",
          nomeDaPropriedade));

      return property.GetValue(instância, null);
    }

    protected void TestarSerialização(T instância, Dictionary<string, object> valores)
    {
      if(instância == null)
        throw new ArgumentException(string.Format("Parâmetro '{0}' não pode ser nulo.",
          "instância"));

      if(valores.Count > 0)
      {
        T entidadeDesmaterializada;

        //Atribui todos os valores a entidade
        foreach(KeyValuePair<string, object> pair in valores)
        {
          string propertyName = pair.Key;
          object propertyValue = pair.Value;

          AtribuirValorNaPropriedade(instância, propertyName, propertyValue);
        }

        //Serializa objeto em memória
        Stream stream = new MemoryStream();
        try
        {
          //Serializa a entidade
          BinaryFormatter formatter = new BinaryFormatter();
          formatter.Serialize(stream, instância);

          //Remove referência a entidade serializada
          stream.Position = 0;

          //Deserializa entidade
          entidadeDesmaterializada = (T)formatter.Deserialize(stream);
        }
        finally
        {
          stream.Dispose();
        }

        //Testa os valores de todas as propriedades sob teste
        object valorRetornado;
        foreach(KeyValuePair<string, object> pair in valores)
        {
          string propertyName = pair.Key;
          object propertyValue = pair.Value;

          valorRetornado = ObterValorDaPropriedade(entidadeDesmaterializada, propertyName);
          Assert.AreEqual(propertyValue, valorRetornado);
        }
      }
    }

    protected void TestarGetObjectData(T instância, Dictionary<string, object> valores)
    {
      if(instância == null)
        throw new ArgumentException(string.Format("Parâmetro '{0}' não pode ser nulo.", "instância"));

      if(!typeof(ISerializable).IsAssignableFrom(TipoEntidade))
        throw new ArgumentException("Entidade não implementa a interface ISerializable.");

      if(valores.Count > 0)
      {
        //Atribui todos os valores a entidade
        foreach(KeyValuePair<string, object> pair in valores)
        {
          string propertyName = pair.Key;
          object propertyValue = pair.Value;

          AtribuirValorNaPropriedade(instância, propertyName, propertyValue);
        }

        //Testa os valores de todas as propriedades sob teste
        object valorRetornado;
        MethodInfo method = TipoEntidade.GetMethod("GetObjectData");
        if(method != null)
        {
          SerializationInfo info = new SerializationInfo(TipoEntidade, new FormatterConverter());
          StreamingContext context = new StreamingContext();
          object[] parâmetros = new Object[] { info, context };
          method.Invoke(instância, parâmetros);
          foreach(KeyValuePair<string, object> pair in valores)
          {
            valorRetornado = info.GetValue(pair.Key, typeof(object));
            Assert.AreEqual(pair.Value, valorRetornado);
          }
        }
        else
        {
          throw new Exception("Método 'GetObjectData' não pode ser localizado.");
        }
      }
    }
  }
}
